# analyzer.py
import requests
import base64
from ollama import Client
from openai import OpenAI
from typing import Dict, List
import json
import re

# from configs import get_config


prompts = [
    # 城市施工识别    
    """
    你是一个专业的城市施工监控视频分析助手。请严格根据图像内容，判断当前画面中是否发生以下事件之一：
    [市政施工, 道路施工, 轨道施工, 建筑施工, 城市更新施工, 园林绿化施工, 交通设施施工,应急抢险施工,其他施工]
    - 只允许选择一个最匹配的事件类型。
    - 剔除低级无效的误报事件
    - 排除正常临停，排除非施工现场等情况
    - 对识别到的事件进行风险评估（高风险/中风险/低风险/正常）
    - 提供准确的边界框坐标和位置描述
    输出格式要求，严格按照以下JSON格式输出events数组，每个事件包含：
    - confidence: 置信度(0-1)
    - event_type: 事件类型
    - event_level: 风险等级
    - event_time: 时间戳(YYYY-MM-DD HH:MM:SS)
    - location_description: 位置描述
    - normalized_box: 归一化边界框坐标
    - description: 详细事件描述
    - 不生成任何其他文本,示例输出json { "is_happen": true, "events": [ { "confidence": 0.95, "event_type": "市政施工-水务管线施工", "event_level": "高风险", "event_time": "2023-04-01 10:30:00", "location_description": "高架桥下中间车道", "normalized_box": { "x_center": 0.45, "y_center": 0.55, "width": 0.15, "height": 0.3 }, "description": "视频中显示了南向北第一车道有一破损轮胎。" } ] }
    现在请分析图像：
    """,
    # 10/28
    """
你是一个专业的省道交通分析AI，基于2帧间隔10秒的图像分析省道的交通事件。需要在控制误报和避免漏报之间取得平衡。
核心任务
基于连续2帧图像，合理判断是否发生以下7类事件：
["交通事故", "道路拥堵", "道路施工", "抛洒物", "道路病害", "设施损坏", "绿化遮挡"]
平衡性判断规则
交通事故判断：
● 排除：车辆正常堆叠、近距离跟车、正常调头、正常停车、视角重叠
● 报告条件（满足任三）：
✓ 车辆异常位置（横置、斜置阻塞多车道，且非正常行驶方向）
✓ 可见明显碰撞变形、碎片或零件散落
✓ 有人员聚集查看+车辆异常停止（非上下客）
✓ 警示三角架、应急灯开启等辅助证据，且车辆非正常停放

道路拥堵判断：
● 排除： 路口等待、红绿灯正常排队
● 报告条件：
✓ 多车道排队长度持续增长影响300米以上
✓ 多车道同时出现完全停滞（非缓行）

抛洒物识别判断：
● 排除：车辆阴影、水渍反光、小型纸屑、单帧出现
● 报告条件：
✓ 独立于车辆的明显障碍物（货物、轮胎等）
✓ 尺寸≥0.3米×0.3米且影响行车安全
✓ 在固定位置2帧持续存在

设施损坏合理判断：
● 排除：完整结构杆件、正常反光、横向灯杆、横向门架、临时遮挡
● 报告条件：
✓ 明显倾斜≥30度或断裂
✓ 部件缺失影响功能（如信号灯不亮、标志牌缺失）
✓ 伴有垂落线缆、碎片等危险迹象

道路病害判断：
● 排除：阴影、水渍
● 报告条件：
✓ 可见路面裂缝、坑槽、凹陷等
✓ 影响行车安全，如车辆绕行或颠簸
✓ 持续存在多帧

绿化遮挡判断：
● 排除：车道外正常绿化
● 报告条件：
✓ 树木、灌木等遮挡交通标志、信号灯或关键视线区域
✓ 遮挡程度影响驾驶员安全识别
✓ 持续存在多帧

置信度调整
● 高置信度(≥0.8)：明确证据，多特征吻合
● 中置信度(0.6-0.79)：有较强迹象但需谨慎
● 输出阈值：≥0.65可报告事件

输出如下JSON，不添加其他文本:
{
  "is_happen": true,
  "events": [
    {
      "confidence": 0.65,  // 降低阈值到0.6
      "event_type": "道路拥堵",
      "event_level": "中",
      "event_time": "2024-01-20 10:30:00",
      "location_description": "省道S201与XX路交叉口北侧",
      "normalized_box": {"x_center":0.45,"y_center":0.55,"width":0.15,"height":0.3},
      "description": "车辆排队超过200米，多车道停滞，持续2帧未见明显流动"
    }
  ]
}
    """,
    # 11/07
    """
    你是一个专业交通分析AI，基于间隔10秒的2帧图像分析交通事件。合理判断是否发生以下7类事件：
["交通事故", "道路拥堵", "道路施工", "抛洒物", "道路病害", "设施损坏", "绿化遮挡"]。目标：平衡误报与漏报，仅当证据明确或强迹象时报告。
总体原则  
● 事件必须在道路内且2帧持续存在。  
● 置信度：高(≥0.8)=多条件明确无排除；中(0.6-0.79)=部分条件有不确定性。  
● 全局排除：道路外、阴影、反光、天气影响、模糊、正常交通流。
事件规则  
1. 交通事故  
  ○ 排除：车辆调头、停车位、红绿灯停车、跟车(间距≥2米)、视角重叠。  
  ○ 报告(需≥2项)：位置异常(阻塞≥2车道)、物理损坏、人员聚集(≥3人)、辅助标志(三角架/应急灯)。  
  ○ 置信度：高(≥2项+无排除)；中(1项+辅助迹象)。
2. 道路拥堵  
  ○ 排除：路口正常排队
  ○ 报告(全部)：停滞>60秒、长度≥200米或多车道停滞、次帧增长≥10%。  
  ○ 置信度：高(全部+源头可见)；中(仅时间+空间)。
3. 抛洒物  
  ○ 排除：阴影、反光、尺寸<0.3米。  
  ○ 报告(全部)：物体独立、尺寸≥0.3米、位置固定(移动≤0.5米)。  
  ○ 置信度：高(清晰+达标)；中(尺寸边界或轻微移动)。
4. 设施损坏  
  ○ 排除：完整结构、正常反光。  
  ○ 报告(≥1项)：结构变形(倾斜≥30度或断裂)、功能缺失、危险迹象(线缆/碎片)。  
  ○ 置信度：高(任一明确)；中(轻微或模糊)。
5. 道路病害  
  ○ 排除：阴影、水渍。  
  ○ 报告(≥1项)：病害可见(裂缝≥1cm/坑槽≥5cm)、安全影响(车辆绕行/颠簸)、多帧持续。  
  ○ 置信度：高(清晰+行为证据)；中(仅病害可见)。
6. 绿化遮挡  
  ○ 排除：车道外绿化、落叶。  
  ○ 报告(全部)：遮挡标志/信号灯、遮蔽≥50%、多帧持续。  
  ○ 置信度：高(完全遮蔽)；中(部分遮蔽30-50%)。
7. 道路施工  
  ○ 排除：临时停靠、日常养护、已完工设备。  
  ○ 报告(需≥2项)：施工标识(锥桶≥3个/标志牌)、人员设备(≥2人/机械)、作业迹象(开挖/封闭)、多帧持续。  
  ○ 置信度：高(标识+人员/作业)；中(标识+封闭或人员+作业缺标识)。
输出如下JSON，不添加其他文本:
{
  "is_happen": true,
  "events": [
    {
      "confidence": 0.65, 
      "event_type": "道路拥堵",
      "event_level": "中",
      "event_time": "2024-01-20 10:30:00",
      "location_description": "省道S201与XX路交叉口北侧",
      "normalized_box": {"x_center":0.45,"y_center":0.55,"width":0.15,"height":0.3},
      "description": "车辆排队超过200米，多车道停滞，持续2帧未见明显流动"
    }
  ]
}
    """,
    """
你是一个专业的省道交通分析AI，专门基于2帧连续图像分析交通事件。必须严格避免误报，特别关注以下常见误判场景。
核心任务
严格分析连续2帧图像，仅当明确证据时判断发生以下7类事件：
["交通事故", "道路拥堵", "道路施工", "抛洒物", "道路病害", "设施损坏", "绿化遮挡"]
关键误报规避规则
针对交通事故误报：
● 严禁将车辆堆叠、遮挡或视角重叠误判为事故
● 必须同时满足以下条件才可判定为交通事故：
(1) 至少一帧显示车辆碰撞、翻覆、严重变形或零件散落
(2) 连续帧中涉事车辆位置异常（如突然偏移、旋转）
(3) 有人员聚集、警示标志等辅助证据
● 正常行驶中的车辆近距离跟车、路口转弯车辆交汇均不算事故
针对道路拥堵误报：
● 红绿灯路口正常排队候车绝不判定为拥堵
● 拥堵必须满足：
(1) 车辆停滞时间超过正常信号周期（约2分钟）
(2) 排队长度持续增加且影响上游交通
(3) 多车道同时出现长时间停滞
● 早晚高峰期的常态缓行不视为拥堵事件
针对抛洒物误报：
● 车辆本体、阴影、倒影、水渍绝不判定为抛洒物
● 抛洒物必须：
(1) 是独立于车辆的明显物体（如货物、碎片）
(2) 在连续帧中位置固定或移动轨迹异常
(3) 尺寸足以影响交通安全（不小于0.3米×0.3米）
● 快速通过的车辆携带物品不视为抛洒
针对设施损坏误报：
● 路灯杆、信号灯横杆等设施的完整结构绝不判定为损坏
● 设施损坏必须：
(1) 可见明显结构断裂、倾斜≥15度或部件缺失
(2) 在连续帧中损坏状态持续
(3) 伴有碎片、线缆垂落等辅助证据
● 反光、阴影造成的视觉误差需排除
通用分析原则
1. 双重确认机制：必须2帧均显示相同事件特征，且置信度≥0.8（高于原0.7）
2. 持续性验证：事件需在时间维度持续发展，排除瞬时现象
3. 环境上下文：结合道路类型、交通流量等场景信息综合判断
4. 保守优先：任何不确定时默认不报告，严控误报率
输出要求
严格按JSON格式输出：
● is_happen: 布尔值（仅当完全确认时=true）
● events: 事件数组（无事件时务必为空数组）
  ○ confidence: 置信度(0-1)，必须≥0.8
  ○ event_type: 事件类型
  ○ event_level: 风险等级（高/中/低）
  ○ event_time: 时间戳(YYYY-MM-DD HH:MM:SS)
  ○ location_description: 位置描述
  ○ normalized_box: 归一化边界框
  ○ description: 具体事件描述
边界框说明
基于图像尺寸归一化到[0,1]范围：
{
  "x_center": 0.45,
  "y_center": 0.55,
  "width": 0.15,
  "height": 0.3
}
只输出JSON，不添加任何其他解释文本。
    """,
    # 省道交通识别
    """
    你是一个专业的省道交通分析AI，专门基于连续图像帧分析交通事件。

# 核心任务
严格分析连续图像，判断是否发生以下7类事件之一：
["交通事故", "道路拥堵", "道路施工", "抛洒物", "道路病害", "设施损坏", "绿化遮挡"]

# 分析规则
1. 事件选择：只输出确定发生，且置信度最高的一个事件，置信度≥0.7
2. 剔除条件：排除路口等待、临时停靠、工程车路过、正常车流，避免误报
3. 画面关联：结合多帧信息，持续性判断确认是否真实发生，宁可不报告也不误报
4. 风险评估：
   - 高风险：可能立即导致事故（交通事故、大型抛洒物）
   - 中风险：影响交通流畅度（道路拥堵、施工）
   - 低风险：轻微影响（绿化遮挡、设施损坏）

# 输出要求
严格按JSON格式输出，包含：
- is_happen: 布尔值，表示是否发生事件
- events: 事件数组（无事件时为空）
  - confidence: 置信度(0-1)
  - event_type: 事件类型
  - event_level: 风险等级
  - event_time: 时间戳(YYYY-MM-DD HH:MM:SS)
  - location_description: 位置描述
  - normalized_box: 归一化边界框
  - description: 事件描述

# 边界框说明
基于图像尺寸归一化到[0,1]范围：
{
  "x_center": 0.45,  # 中心点X坐标
  "y_center": 0.55,  # 中心点Y坐标  
  "width": 0.15,     # 宽度
  "height": 0.3      # 高度
}

只输出JSON，不添加任何其他文本。
    """
    # 国省道施工识别
    """
    你是省道交通AI助手。严格根据视频内容，严格判断画面是否发生以下事件之一：
    [交通事故, 道路拥堵, 道路施工, 抛洒物, 道路病害, 设施损坏, 绿化遮挡]
    - 只选一个最匹配事件。
    - 剔除红绿灯等待、临停、施工车辆路过等情况
    - 剔除低级误报事件
    - 排除正常交通流和无关对象
    - 对识别到的事件进行风险评估（高风险/中风险/低风险/正常）
    - 提供精确的边界框坐标和位置描述
    输出格式要求,严格按照以下JSON格式输出events数组，每个事件包含：
    - confidence: 置信度(0-1)
    - event_type: 事件类型
    - event_level: 风险等级
    - event_time: 时间戳(YYYY-MM-DD HH:MM:SS)
    - location_description: 位置描述
    - normalized_box: 归一化边界框坐标
    - description: 详细事件描述
    - 不生成无关文本,示例json { "is_happen": true, "events": [ { "confidence": 0.95, "event_type": "路面抛洒物", "event_level": "高风险", "event_time": "2023-04-01 10:30:00", "location_description": "中间车道", "normalized_box": { "x_center": 0.45, "y_center": 0.55, "width": 0.15, "height": 0.3 }, "description": "视频中显示了南向北第一车道有一破损轮胎。" } ] }
    """,
]



def parse_message(message):
    """
    解析报文并返回JSON字典结果
    
    Args:
        message: 包含JSON数据的字符串报文
        
    Returns:
        dict: 解析后的JSON字典
    """
    try:
        # 方法1: 使用正则表达式提取JSON部分
        json_pattern = r'```json\s*(\{.*?\})\s*```'
        match = re.search(json_pattern, message, re.DOTALL)
        
        if match:
            json_str = match.group(1)
            # 解析JSON字符串
            result = json.loads(json_str)
            return result
        else:
            # 方法2: 如果没有代码块标记，尝试直接解析整个字符串
            # 先清理可能的额外内容
            cleaned_message = message.strip()
            # 尝试找到第一个{和最后一个}
            start_idx = cleaned_message.find('{')
            end_idx = cleaned_message.rfind('}') + 1
            
            if start_idx != -1 and end_idx != 0:
                json_str = cleaned_message[start_idx:end_idx]
                result = json.loads(json_str)
                return result
            else:
                raise ValueError("未找到有效的JSON数据")
                
    except json.JSONDecodeError as e:
        raise ValueError(f"JSON解析错误: {e}")
    except Exception as e:
        raise ValueError(f"解析失败: {e}")
    

def analyze_with_ollama(image_paths: List[str],model_host:str, model:str, prompt_input: str) -> str:
    """
    将三张图像上传给 qwen-vl，联合分析
    """
    try:
        client = Client(
        host=model_host,
        headers={'x-some-header': 'some-value'})        
        
        response = client.generate(model, prompt=prompt_input,images=image_paths, stream=False)
        print(f"qwen response 111: {response}")

        if not response is None:
            # reply = response['message']['content']
            reply = response['response']
            print(f"qwen reply222: {reply}")
            return parse_message(reply)
        
        else:
            return {"is_happen": False, "error": f"HTTP {response}"}
        
    except Exception as e:
        return {"is_happen": False, "error": str(e)}

def analyze_keyframe_with_openai(image_paths: List[str],model_host:str, model_name:str,  prompt_input: str) -> dict:
    """调用 OpenAI 判断是否为施工"""

    try:
       
        client = OpenAI(
            api_key="",  # vLLM 无需认证密钥，任意字符串均可[1,3](@ref)
            base_url= model_host  # 与 vLLM 服务端口一致[1,3](@ref)
        )
                
        # models = client.models.list()  # 测试连接
        # print(f"qwen models: {models}")
                
        response = client.chat.completions.create(
            model=model_name,
            messages=[{
                    "role": "user",
                    "content": [
                        {"type": "text", "text": prompt_input},
                        # {"type": "video_url", "video_url": {"url": "file://"+image_paths}},
                        {"type": "image_url", "image_url": {"url": "file://"+image_paths[0]}},
                        {"type": "image_url", "image_url": {"url": "file://"+image_paths[1]}},
                    ],
                }],
            stream=False
        )
        if not response is None:
            # reply = response['message']['content']
            reply = response.choices[0].message.content
            print(f"qwen reply222: {reply}")
            return parse_message(reply)
        else:
             return {"is_happen": False, "error": f"HTTP {response}"}
        
    except Exception as e:
        return {"is_happen": False, "error": str(e)}

def analyze_with_qwen(model_type: str,image_paths: List[str],model_host:str, model:str, prompt_index: int,events: str = '') -> str:
    
    prompt_input = prompts[prompt_index]
    
    if events and events.strip():
        reported_events = "\n剔除已报事件：" + events
        prompt_input += reported_events
    
    if model_type == "ollama":
        return analyze_with_ollama(image_paths,model_host, model, prompt_input)
    elif model_type == "openai":
        return analyze_keyframe_with_openai(image_paths,model_host, model, prompt_input)
    else:
        raise ValueError(f"不支持的模型类型: {model_type}")

if __name__ == '__main__':
    # 测试代码
    print("ollama test")
    r = analyze_with_qwen("ollama",["/data/tmp/test_cz3.jpeg"],"http://localhost:11434","qwen2.5vl",0,"施工")
    print(r)
    # print(r)
    
